package com.green_reading.webrtcclient;

import android.util.Base64;
import android.util.Log;

import com.green_reading.webrtcclient.WebrtcEvent.WebrtcCallback;

import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Timestamp;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class WebRtcClient {
    private final static String TAG = WebRtcClient.class.getCanonicalName();
    private PeerConnectionFactory factory;
    private LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<>();
    private PeerConnectionParameters peerConnectionParameters;
    private MediaConstraints pcConstraints;
    private MediaStream localMediaStream;
    private VideoTrack localVideoTrack;
    private AudioTrack localAudioTrack;
    private CameraVideoCapturer videoCapturer;
    private boolean videoCapturerStopped;
    private VideoSource videoSource;
    EglBase.Context renderEGLContext;

    private RtcListener mListener;
    private WebSocketClient websocketClient;
    private boolean loginInSuccess = false;
    private String userName;
    private String peerName;
    private Peer peer;
    private final ScheduledExecutorService executor;
    private final ScheduledExecutorService executorSaveData;
    private final ScheduledExecutorService executorPing;
    private static final WebRtcClient instance = new WebRtcClient();
    private PeerConnection pc;
    private String turn_key = "password";
    private String host;

    private String fileName;
    private int fileSize;
    private int fileSizeCurrent = 0;
    private DataChannel dataChannelLocal;

    private boolean isDownloadFile = false;
    private String savePath;
    private String realFileSavePath;
    private WebrtcCallback webrtcCallback;
    private boolean createWebrtcCalled = false;
    public boolean hasCallInit() {return createWebrtcCalled;}

    public boolean isLogined() {return loginInSuccess;}
    public boolean isConnected() {
        return websocketClient != null &&
                websocketClient.getReadyState() == WebSocket.READYSTATE.OPEN;
    }

    public boolean startVideo() {return true;}

    private void cleanFileTransformParameter() {
        fileName = "";
        fileSize = 0;
        fileSizeCurrent = 0;
        isDownloadFile = false;
        savePath = "";
        realFileSavePath = "";
    }
    public int requestGetFile(String fileName_, String savePath) {
        if(isDownloadFile) {
            return -1;
        }
        cleanFileTransformParameter();
        this.savePath = savePath;
        try {
            JSONObject payload = new JSONObject();
            payload.put("fileName", fileName_);
            sendMessage("file-transform", payload);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return 0;
    };

    public void cancelFile() {
        Log.d(TAG, "cancelFile");
        if(!isDownloadFile) {
            return;
        } else {
            cleanFileTransformParameter();
            webrtcCallback.onFileAbort();
            Log.d(TAG, "cancelFile");
            JSONObject payload = new JSONObject();
            sendMessage("file-transform-cancel", payload);
            return;
        }
    }

    /* Implement this interface to be notified of events. */
    public interface RtcListener{
        void onStatusChanged(String newStatus);
        void onAddRemoteStream(MediaStream remoteStream);
        void onRemoveRemoteStream();
        void onPeerLeave();
    }

    private void handleLogin(JSONObject payload) throws JSONException {
        Log.d(TAG, "handleLogin");
        boolean login_result = payload.getBoolean("success");
        if(login_result == true){
            loginInSuccess = true;
            webrtcCallback.onLogin();
            webrtcCallback.onConnect();
            executorPing.execute(new Runnable() {
                @Override
                public void run() {
                    while(loginInSuccess) {
                        try {
                            if(!isConnected()) {break;}
                            final JSONObject payload = new JSONObject();
                            payload.put("type", "ping-signal");
                            executor.execute(new Runnable() {
                                @Override
                                public void run() {
                                    websocketClient.send(payload.toString());
                                }
                            });
                            Thread.sleep(10000);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            Log.d(TAG, "LoginCommand: success");
        } else{
            Log.d(TAG, "Ooops...try a different username");
            sendLoginSignal();
            //loginInSuccess = false;
        }
    }

    private void handleOffer(final JSONObject payload) throws JSONException {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SessionDescription sdp = new SessionDescription(
                            SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
                            payload.getString("sdp")
                    );
                    pc.setRemoteDescription(peer, sdp);
                    pc.createAnswer(peer, pcConstraints);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        Log.d(TAG,"handleOffer");
    }

    private void handleAnswer(final JSONObject payload) throws JSONException {
        Log.d(TAG,"handleAnswer");
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SessionDescription sdp = new SessionDescription(
                            SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
                            payload.getString("sdp")
                    );
                    pc.setRemoteDescription(peer, sdp);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void handleCandidate(final JSONObject payload) throws JSONException {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (pc.getRemoteDescription() != null) {
                        IceCandidate candidate = new IceCandidate(
                                payload.getString("sdpMid"),
                                payload.getInt("sdpMLineIndex"),
                                payload.getString("candidate"));
                        pc.addIceCandidate(candidate);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        Log.d(TAG,"handleCandidate");
    }

    private void handleFileTransform(final JSONObject payload) throws JSONException {
        fileName = payload.getString("fileName");
        fileSize = payload.getInt("fileSize");
        webrtcCallback.onFileAccept();
        sendMessage("file-transform-parameter-accept", new JSONObject());
    }

    private void handleLeave() {
        mListener.onPeerLeave();
    }

    /**
     * Send a message through the signaling server
     * @param type type of message
     * @param payload payload of message
     */
    public void sendMessage(String type, JSONObject payload) {
        if(!isConnected()) {
            return;
        }
        try {
            final JSONObject message = new JSONObject();
            String to = peerName;
            message.put("name", to);
            message.put("type", type);
            message.put("payload", payload);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(message.toString());
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public boolean sendMessage(String payload) {
        if(!isConnected()) {
            return false;
        }
        try {
            final JSONObject message = new JSONObject();
            String to = peerName;
            message.put("name", to);
            message.put("type", "message");
            message.put("payload", payload);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(message.toString());
                }
            });
            return true;
        } catch (JSONException e) {
            e.printStackTrace();
            return false;
        }
    }

    private void getMessage(JSONObject data){
        try {
            String type = data.getString("type");
            String from = data.optString("from");
            JSONObject payload = null;
            payload = data.optJSONObject("payload");
            // if peer is unknown, try to add him
            if(!from.equals("") && (peerName == null || !peerName.equals(from))) {
                Log.d(TAG,"peerName logic ERROR: " + "peerName: " + peerName +
                        "message from: " + from);
            }
            if(!from.equals("") && (peerName == null || !peerName.equals(from))) {
                Log.d(TAG,"peerName != from:" + peerName + from);
                peerName = from;
                startWebrtc(from, true);
            }
            if(pc == null) {
                Thread.sleep(500);
            }

            switch(type){
            case "login":
                handleLogin(data);
		        break;
            case "offer":
                handleOffer(payload);
                break;
            case "answer":
                handleAnswer(payload);
                break;
            case "candidate":
                handleCandidate(payload);
                break;
            case "file-transform-parameter":
                handleFileTransform(payload);
                break;
            case "file-transform-file-size-zero":
                webrtcCallback.onFileSizeError();
                cleanFileTransformParameter();
                break;
            case "leave":
                handleLeave();
                break;
            case "message":
                webrtcCallback.onNotify(data.optString("payload"));
                break;
            case "pong-signal":
                break;
            case "peer-excced-3":
                webrtcCallback.onMessage("连接的用户超过3人,请稍后再试");
                break;
            case "peer-not-exist":
                webrtcCallback.onMessage("设备不存在,请重新输入设备名");
                 break;
            default:
                Log.d(TAG, "unkonw message type");
                break;
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }  catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    private class Peer implements SdpObserver, PeerConnection.Observer, DataChannel.Observer{
        /* Called on success of Create{Offer,Answer}(). implements SdpObserver interface*/
        @Override
        public void onCreateSuccess(final SessionDescription sdp) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        String sdpString = sdp.description;
                        sdpString = preferCodec(
                                sdpString, peerConnectionParameters.videoCodec, false);
                        if(sdpString.equals("")) {
                            JSONObject payload = new JSONObject();
                            sendMessage("leave", payload);
                            webrtcCallback.onMessage("您的设备不支持视频解码，无法观看视频");
                            webrtcCallback.onDisconnected();
                            stopWebrtc();
                            return;
                        }
                        //sdpString = preferCodec(
                        //        sdpString, peerConnectionParameters.audioCodec, true);
                        JSONObject payload = new JSONObject();
                        payload.put("type", sdp.type.canonicalForm());
                        payload.put("sdp", sdpString);
                        sendMessage(sdp.type.canonicalForm(), payload);
                        executor.execute(new Runnable() {
                            @Override
                            public void run() {
                                pc.setLocalDescription(Peer.this, sdp);
                            }
                        });
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });
            Log.d(TAG, "onCreateSuccess");
        }

        @Override
        public void onSetSuccess() {Log.d(TAG, "onSetSuccess");}
        @Override
        public void onCreateFailure(String s) {Log.d(TAG, "onCreateFailure: " + s);}
        @Override
        public void onSetFailure(String s) {Log.d(TAG, "onSetFailure");}


        /* implement PeerConnection.Observer interface */
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {}

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            if(iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
                removePeer();
                webrtcCallback.onDisconnected();
                mListener.onStatusChanged("DISCONNECTED");
            }
        }

        @Override
        public void onIceConnectionReceivingChange(boolean var1) {};

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {}

        @Override
        public void onIceCandidate(final IceCandidate candidate) {
            try {
                JSONObject payload = new JSONObject();
                payload.put("sdpMLineIndex", candidate.sdpMLineIndex);
                payload.put("sdpMid", candidate.sdpMid);
                payload.put("candidate", candidate.sdp);
                sendMessage("candidate", payload);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] var1) {};

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.d(TAG,"onAddStream "+mediaStream.label());
            Log.d(TAG,"onAddStream "+mediaStream.toString());

            // remote streams are displayed from 1 to MAX_PEER (0 is localStream)
            mListener.onAddRemoteStream(mediaStream);
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.d(TAG,"onRemoveStream "+mediaStream.label());
            removePeer();
            mListener.onRemoveRemoteStream();
        }

        @Override
        public void onDataChannel(DataChannel dc) {dataChannelLocal = dc;}

        @Override
        public void onRenegotiationNeeded() {}

        @Override
        public void onAddTrack(RtpReceiver var1, MediaStream[] var2) {}

        /* implement DataChannel.Observer interface */
        @Override
        public void onBufferedAmountChange(long var1) {
            Log.d(TAG, "onBufferedAmountChange: " + var1);
        }

        @Override
        public void onStateChange() {
            Log.d(TAG, "onStateChange" + dataChannelLocal.state());
            if (dataChannelLocal.state() == DataChannel.State.OPEN) {
            }
        }

        /**
         * A data buffer was successfully received.  NOTE: |buffer.data| will be
         * freed once this function returns so callers who want to use the data
         * asynchronously must make sure to copy it first.
         */
        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.d(TAG, "onMessage" + buffer);
            final ByteBuffer clone = ByteBuffer.allocate(buffer.data.capacity());
            buffer.data.rewind();   //copy from the beginning
            clone.put(buffer.data);
            buffer.data.rewind();
            executorSaveData.execute(new Runnable() {
                @Override
                public void run() {
                    saveData(clone);
                }
            });
        }

        public Peer() {
            Log.d(TAG,"new Peer: " + peerName);
            mListener.onStatusChanged("CONNECTING");
        }

        private String preferCodec(String sdpDescription, String codec, boolean isAudio) {
            String[] lines = sdpDescription.split("\r\n");
            int mLineIndex = -1;
            String codecRtpMap = null;
            // a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
            String regex = "^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$";
            Pattern codecPattern = Pattern.compile(regex);
            String mediaDescription = "m=video ";
            if (isAudio) {
                mediaDescription = "m=audio ";
            }
            for (int i = 0; (i < lines.length)
                    && (mLineIndex == -1 || codecRtpMap == null); i++) {
                if (lines[i].startsWith(mediaDescription)) {
                    mLineIndex = i;
                    continue;
                }
                Matcher codecMatcher = codecPattern.matcher(lines[i]);
                if (codecMatcher.matches()) {
                    codecRtpMap = codecMatcher.group(1);
                }
            }
            if (codecRtpMap == null || mLineIndex == -1) {
                Log.w(TAG, "No rtpmap for " + codec);
                return "";
                //return sdpDescription;
            }
            Log.d(TAG, "Found " +  codec + " rtpmap " + codecRtpMap + ", prefer at "
                    + lines[mLineIndex]);
            String[] origMLineParts = lines[mLineIndex].split(" ");
            if (origMLineParts.length > 3) {
                StringBuilder newMLine = new StringBuilder();
                int origPartIndex = 0;
                // Format is: m=<media> <port> <proto> <fmt> ...
                newMLine.append(origMLineParts[origPartIndex++]).append(" ");
                newMLine.append(origMLineParts[origPartIndex++]).append(" ");
                newMLine.append(origMLineParts[origPartIndex++]).append(" ");
                newMLine.append(codecRtpMap);
                for (; origPartIndex < origMLineParts.length; origPartIndex++) {
                    if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
                        newMLine.append(" ").append(origMLineParts[origPartIndex]);
                    }
                }
                lines[mLineIndex] = newMLine.toString();
                Log.d(TAG, "Change media description: " + lines[mLineIndex]);
            } else {
                Log.e(TAG, "Wrong SDP media description format: " + lines[mLineIndex]);
            }
            StringBuilder newSdpDescription = new StringBuilder();
            for (String line : lines) {
                newSdpDescription.append(line).append("\r\n");
            }
            return newSdpDescription.toString();
        }
    }

    private long getTimeStamp(String host_add) {
        BufferedReader in = null;
        try {
            URL url = new URL("http://" + host_add);
            Log.d(TAG, "http://" + host_add);
            URLConnection urlConnection = url.openConnection();
            urlConnection.setRequestProperty("accept", "*/*");
            urlConnection.setRequestProperty("connection", "Keep-Alive");
            urlConnection.setRequestProperty("user-agent",
                    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14)");
            urlConnection.connect();
            in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String line;
            String result = "";
            while (null != (line = in.readLine())) {
                result += line;
            }
            Log.d(TAG, "getTimeStamp: " + result);
            String returnLable = "timestamp_is_";
            String timeStampString = result.substring(
                        result.indexOf(returnLable) + returnLable.length(), result.length());
            return Long.parseLong(timeStampString);
        } catch (Exception e) {
            Log.e(TAG, "发送GET请求出现异常！" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }

        // http get request failed when go here, using system timestamp
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        return ts.getTime() / 1000;
    }

    private CameraVideoCapturer createCapturer(CameraEnumerator enumerator) {
        CameraVideoCapturer videoCapturer = null;
        final String[] deviceNames = enumerator.getDeviceNames();

        // First, try to find front facing camera
        Logging.d(TAG, "Looking for front facing cameras.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating front facing camera capturer.");
                videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        // Front facing camera not found, try something else
        Logging.d(TAG, "Looking for other cameras.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating other camera capturer.");
                videoCapturer = enumerator.createCapturer(deviceName, null);

                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    private void addPeer(final boolean loopback) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                peer = new Peer();
                //sync timestamp
                long timeStamp = getTimeStamp(host + ":3033/timestamp");
                String coturnUserName = Long.toString(timeStamp);
                coturnUserName += (":" + userName);
                String password = "";
                Log.d(TAG, timeStamp + "timeStamp1");
                try {
                    password = hmacSha1Encrypt(coturnUserName, turn_key);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Log.d(TAG, timeStamp + "timeStamp2");
                String server_url = "turn:" + host;
                Log.d(TAG, coturnUserName + password);
                iceServers.add(new PeerConnection.IceServer(server_url + ":3478?transport=udp",
                        coturnUserName, password));
                iceServers.add(new PeerConnection.IceServer(server_url + ":3478?transport=tcp",
                        coturnUserName, password));
                iceServers.add(new PeerConnection.IceServer(server_url + ":3479?transport=udp",
                        coturnUserName, password));
                iceServers.add(new PeerConnection.IceServer(server_url + ":3479?transport=tcp",
                        coturnUserName, password));

                pc = factory.createPeerConnection(iceServers, pcConstraints, peer);
                Log.d(TAG, pc.toString() + " pc ptr");


                localMediaStream = factory.createLocalMediaStream("ARDAMS");
                if(peerConnectionParameters.videoCallEnabled){
                    factory.setVideoHwAccelerationOptions(renderEGLContext, renderEGLContext);
                    videoCapturer = createCapturer(new Camera1Enumerator(peerConnectionParameters.captureToTexture));
                    videoSource = factory.createVideoSource(videoCapturer); //, videoConstraints);
                    videoCapturer.startCapture(peerConnectionParameters.videoWidth,
                            peerConnectionParameters.videoHeight,
                            peerConnectionParameters.videoFps);
                    localVideoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
                    localMediaStream.addTrack(localVideoTrack);
                }

                AudioSource audioSource = factory.createAudioSource(new MediaConstraints());
                localAudioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
                //localAudioTrack.setEnabled(enableAudio);
                localMediaStream.addTrack(localAudioTrack);

                DataChannel.Init dataChannelInit = new DataChannel.Init();
                dataChannelInit.id = 1;
                dataChannelLocal = pc.createDataChannel("data_channel", dataChannelInit);
                dataChannelLocal.registerObserver(peer);

                pc.addStream(localMediaStream);
                if(!loopback) {
                    pc.createOffer(peer, pcConstraints);
                }
            }
        });
    }

    private void removePeer() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                peer = null;
            }
        });
    }

    public static String hmacSha1Encrypt(String value, String key) throws Exception {
        byte[] keyBytes = key.getBytes();
        SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(value.getBytes());
        return new String(Base64.encodeToString(rawHmac, Base64.NO_WRAP));
    }

    private void sendLoginSignal() {
        Random rand = new Random();
        int n = rand.nextInt(100);
        userName = Integer.toString(n);
        try {
            final JSONObject payload = new JSONObject();
            payload.put("type", "login");
            payload.put("name", userName);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(payload.toString());
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Call this method in Activity.onPause()
     */
    public void onPause() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                if (videoCapturer != null && !videoCapturerStopped) {
                    Log.d(TAG, "Stop video source.");
                    try {
                        videoCapturer.stopCapture();
                    } catch (InterruptedException e) {}
                    videoCapturerStopped = true;
                }
            }
        });
    }

    /**
     * Call this method in Activity.onResume()
     */
    public void onResume() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                if (videoCapturer != null && videoCapturerStopped) {
                    Log.d(TAG, "Restart video source.");
                    videoCapturer.startCapture(peerConnectionParameters.videoWidth,
                            peerConnectionParameters.videoHeight, peerConnectionParameters.videoFps);
                    videoCapturerStopped = false;
                }
            }
        });
    }

    private void login() {
        if (!loginInSuccess){
            sendLoginSignal();
        } else {
            Log.d(TAG,"logined alreadly");
        }
    }

    public void init(final RtcListener listener, final String socketAddress, String ip,
                     String peerName, WebrtcCallback webrtcCallback,
                     final EglBase.Context renderEGLContext, PeerConnectionParameters params){
        cleanFileTransformParameter();
        this.renderEGLContext = renderEGLContext;
        this.peerConnectionParameters = params;
        this.host = ip;
        this.peerName = peerName;
        this.mListener = listener;
        this.webrtcCallback = webrtcCallback;
        pcConstraints = new MediaConstraints();
        pcConstraints.mandatory.add(
                new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        pcConstraints.mandatory.add(
                new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));

        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("maxWidth", "720"));
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("minWidth", "240"));
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("maxHeight", "720"));
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("minHeight", "320"));
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("echoCancellation", "true"));
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));

        Log.d(TAG,"here");
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (websocketClient == null) {
                        websocketClient =
                                new WebSocketClient(new URI(socketAddress), new Draft_17()) {
                            public void onMessage(String message) {
                                System.out.println("got: " + message + "\n");
                                try {
                                    JSONObject data = new JSONObject(message);
                                    getMessage(data);
                                } catch (JSONException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void onOpen(ServerHandshake handshake) {
                                System.out.println("You are connected to ChatServer: " +
                                        getURI() + "\n");
                            }

                            @Override
                            public void onClose(int code, String reason, boolean remote) {
                                System.out.println("You have been disconnected from: " + getURI() +
                                        "; Code: " + code + " " + reason + "\n");
                            }

                            @Override
                            public void onError(Exception ex) {
                                System.out.println("Exception occured ...\n" + ex + "\n");
                                ex.printStackTrace();
                            }
                        };
                        websocketClient.connect();
                    }
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }
                try {
                    int timeout = 0;
                    while(!isConnected() && timeout < 6) {
                        Thread.sleep(500);
                        timeout =+ 1;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG,"here1");

                if(isConnected()) {
                    Log.d(TAG,"here2");

                    login();
                }
            }
        });
    }

    public void unInit() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                loginInSuccess = false;
                websocketClient.close();
                websocketClient = null;
                webrtcCallback.onLogout();
                pcConstraints = null;
                cleanFileTransformParameter();
            }
        });
    }

    public static WebRtcClient getInstance() {
        return instance;
    }

    private WebRtcClient() {
        // Executor thread is started once in private ctor and is used for all
        // peer connection API calls to ensure new peer connection factory is
        // created on the same thread as previously destroyed factory.
        executor = Executors.newSingleThreadScheduledExecutor();
        executorSaveData = Executors.newSingleThreadScheduledExecutor();
        executorPing = Executors.newSingleThreadScheduledExecutor();
    }

    public void startWebrtc(String peerName, final boolean loopback) {
        this.peerName = peerName;

        cleanFileTransformParameter();
        createWebrtcCalled = true;
        iceServers = new LinkedList<>();

        executor.execute(new Runnable() {
            @Override
            public void run() {
                createWebRtcClientFactoryInternal(loopback);
            }
        });
    }

    public void stopWebrtc() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                if(!createWebrtcCalled) { return; }
                createWebrtcCalled = false;
                if(videoSource != null) {
                    videoSource.dispose();
                    videoSource = null;
                }
                if (pc != null) {
                    pc.dispose();
                    pc = null;
                }
                peer = null;
                if (factory != null) {
                    factory.dispose();
                    factory = null;
                }
                iceServers = new LinkedList<>();
                cleanFileTransformParameter();
                JSONObject payload = new JSONObject();
                sendMessage("leave", payload);
            }
        });
    }

    private void createWebRtcClientFactoryInternal(final boolean loopback) {
        //PeerConnectionFactory.initializeInternalTracer();
        // Initialize field trials.
        videoCapturerStopped = false;
        PeerConnectionFactory.initializeFieldTrials("");
        PeerConnectionFactory.initializeAndroidGlobals(mListener, true, true,
                peerConnectionParameters.videoCodecHwAcceleration);
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        factory = new PeerConnectionFactory(options);
        addPeer(loopback);
    }

    private void saveData(ByteBuffer data) {
        try {
            //Log.d(TAG, "saveData:fileSizeCurrent " + fileSizeCurrent);
            String[] filePathSplit = fileName.split("\\.");
            String[] fileNameExtend = new String[2];
            if (filePathSplit.length == 2) {
                fileNameExtend = filePathSplit;
            } else {
                fileNameExtend[0] = fileName;
                fileNameExtend[1] = "";
            }
            File file;
            if (fileSizeCurrent == 0) {
                realFileSavePath = savePath + fileName;
                file = new File(realFileSavePath);
                int i = 0;
                while (file.exists()){
                    realFileSavePath =
                            savePath + fileNameExtend[0] + "_" + i + "." + fileNameExtend[1];
                    file = new File(realFileSavePath);
                    i++;
                }
                file.createNewFile();
                Log.d(TAG, "realFileSavePath: " + realFileSavePath);
            } else {
                Log.d(TAG, "realFileSavePath: " + realFileSavePath);
                file = new File(realFileSavePath);
            }
            data.flip();
            FileOutputStream fileOutputStream = new FileOutputStream(file, true);
            FileChannel channel = fileOutputStream.getChannel();
            channel.write(data);
            fileOutputStream.close();

            fileSizeCurrent += data.limit();
            Log.d(TAG, "saveData:fileSizeCurrent " + fileSizeCurrent);
            webrtcCallback.onFileProgress(realFileSavePath, fileSize, fileSizeCurrent);
            if (fileSizeCurrent >= fileSize) {
                webrtcCallback.onFileFinish();
                JSONObject payload = new JSONObject();
                sendMessage("save-data-over", payload);
                cleanFileTransformParameter();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
